/**
 * The MOSES project, model based software engineering of networked embedded systems.
 * 
 * Copyright (C) 2010-2013, University of Applied Sciences Wildau,
 * Telematics Research Group, http://www.tm.th-wildau.de/research-group
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses>.
 * 
 * Contributors:
 *     University of Applied Sciences Wildau - initial API and implementation
 * 
 * If you are interested in a commercial license please contact us via
 * e-mail: ralf.vandenhouten@th-wildau.de.
 */
package de.thwildau.tm.moses.asn1.jvmmodel;

import com.google.inject.Inject;
import de.thwildau.tm.moses.asn1.asn1.AnyDefinedNamedType;
import de.thwildau.tm.moses.asn1.asn1.ChoiceType;
import de.thwildau.tm.moses.asn1.asn1.ComponentOfType;
import de.thwildau.tm.moses.asn1.asn1.ComponentType;
import de.thwildau.tm.moses.asn1.asn1.ComponentTypeList;
import de.thwildau.tm.moses.asn1.asn1.DefinedType;
import de.thwildau.tm.moses.asn1.asn1.EnumeratedType;
import de.thwildau.tm.moses.asn1.asn1.EnumerationItem;
import de.thwildau.tm.moses.asn1.asn1.IA5String;
import de.thwildau.tm.moses.asn1.asn1.IntegerType;
import de.thwildau.tm.moses.asn1.asn1.NamedComponentType;
import de.thwildau.tm.moses.asn1.asn1.NamedType;
import de.thwildau.tm.moses.asn1.asn1.OctetStringType;
import de.thwildau.tm.moses.asn1.asn1.SequenceOfType;
import de.thwildau.tm.moses.asn1.asn1.SequenceType;
import de.thwildau.tm.moses.asn1.asn1.Type;
import java.util.Arrays;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtend2.lib.StringConcatenation;
import org.eclipse.xtext.common.types.JvmEnumerationLiteral;
import org.eclipse.xtext.common.types.JvmEnumerationType;
import org.eclipse.xtext.common.types.JvmField;
import org.eclipse.xtext.common.types.JvmGenericType;
import org.eclipse.xtext.common.types.JvmMember;
import org.eclipse.xtext.common.types.JvmOperation;
import org.eclipse.xtext.common.types.JvmTypeReference;
import org.eclipse.xtext.naming.IQualifiedNameProvider;
import org.eclipse.xtext.naming.QualifiedName;
import org.eclipse.xtext.util.Pair;
import org.eclipse.xtext.util.Tuples;
import org.eclipse.xtext.xbase.jvmmodel.AbstractModelInferrer;
import org.eclipse.xtext.xbase.jvmmodel.IJvmDeclaredTypeAcceptor;
import org.eclipse.xtext.xbase.jvmmodel.IJvmDeclaredTypeAcceptor.IPostIndexingInitializing;
import org.eclipse.xtext.xbase.jvmmodel.JvmTypesBuilder;
import org.eclipse.xtext.xbase.lib.Extension;
import org.eclipse.xtext.xbase.lib.Procedures.Procedure1;

/**
 * This is a class that infers the JVM model from the ASN.1 model (i.e. it
 * translates the ASN.1 model to the JVM model). The actual code generation
 * is then done by the org.eclipse.xtext.xbase.compiler.JvmModelGenerator class
 */
@SuppressWarnings("all")
public class Asn1JvmModelInferrer extends AbstractModelInferrer {
  @Inject
  @Extension
  private JvmTypesBuilder _jvmTypesBuilder;
  
  @Inject
  @Extension
  private IQualifiedNameProvider _iQualifiedNameProvider;
  
  public static CharSequence generateClassComment(final QualifiedName qn) {
    StringConcatenation _builder = new StringConcatenation();
    _builder.append("This is a generated data type class for the ASN.1 type ");
    String _lastSegment = qn.getLastSegment();
    _builder.append(_lastSegment, "");
    _builder.newLineIfNotEmpty();
    _builder.append(" ");
    _builder.newLine();
    _builder.append("@generated\t");
    _builder.newLine();
    return _builder;
  }
  
  protected void _infer(final SequenceType element, final IJvmDeclaredTypeAcceptor acceptor, final boolean isPrelinkingPhase) {
    QualifiedName _fullyQualifiedName = this._iQualifiedNameProvider.getFullyQualifiedName(element);
    JvmGenericType _class = this._jvmTypesBuilder.toClass(element, _fullyQualifiedName);
    IPostIndexingInitializing<JvmGenericType> _accept = acceptor.<JvmGenericType>accept(_class);
    final Procedure1<JvmGenericType> _function = new Procedure1<JvmGenericType>() {
        public void apply(final JvmGenericType it) {
          QualifiedName _fullyQualifiedName = Asn1JvmModelInferrer.this._iQualifiedNameProvider.getFullyQualifiedName(element);
          CharSequence _generateClassComment = Asn1JvmModelInferrer.generateClassComment(_fullyQualifiedName);
          String _string = _generateClassComment.toString();
          Asn1JvmModelInferrer.this._jvmTypesBuilder.setDocumentation(it, _string);
          ComponentTypeList _componentTypeList = element.getComponentTypeList();
          EList<ComponentType> _componentTypes = _componentTypeList.getComponentTypes();
          for (final ComponentType ct : _componentTypes) {
            {
              Pair<String,JvmTypeReference> field = Asn1JvmModelInferrer.this.toField(ct);
              EList<JvmMember> _members = it.getMembers();
              String _first = field.getFirst();
              JvmTypeReference _second = field.getSecond();
              JvmField _field = Asn1JvmModelInferrer.this._jvmTypesBuilder.toField(ct, _first, _second);
              Asn1JvmModelInferrer.this._jvmTypesBuilder.<JvmField>operator_add(_members, _field);
              EList<JvmMember> _members_1 = it.getMembers();
              String _first_1 = field.getFirst();
              JvmTypeReference _second_1 = field.getSecond();
              JvmOperation _setter = Asn1JvmModelInferrer.this._jvmTypesBuilder.toSetter(ct, _first_1, _second_1);
              Asn1JvmModelInferrer.this._jvmTypesBuilder.<JvmOperation>operator_add(_members_1, _setter);
              EList<JvmMember> _members_2 = it.getMembers();
              String _first_2 = field.getFirst();
              JvmTypeReference _second_2 = field.getSecond();
              JvmOperation _getter = Asn1JvmModelInferrer.this._jvmTypesBuilder.toGetter(ct, _first_2, _second_2);
              Asn1JvmModelInferrer.this._jvmTypesBuilder.<JvmOperation>operator_add(_members_2, _getter);
            }
          }
        }
      };
    _accept.initializeLater(_function);
  }
  
  protected void _infer(final SequenceOfType element, final IJvmDeclaredTypeAcceptor acceptor, final boolean isPrelinkingPhase) {
    QualifiedName _fullyQualifiedName = this._iQualifiedNameProvider.getFullyQualifiedName(element);
    JvmGenericType _class = this._jvmTypesBuilder.toClass(element, _fullyQualifiedName);
    IPostIndexingInitializing<JvmGenericType> _accept = acceptor.<JvmGenericType>accept(_class);
    final Procedure1<JvmGenericType> _function = new Procedure1<JvmGenericType>() {
        public void apply(final JvmGenericType it) {
          QualifiedName _fullyQualifiedName = Asn1JvmModelInferrer.this._iQualifiedNameProvider.getFullyQualifiedName(element);
          CharSequence _generateClassComment = Asn1JvmModelInferrer.generateClassComment(_fullyQualifiedName);
          String _string = _generateClassComment.toString();
          Asn1JvmModelInferrer.this._jvmTypesBuilder.setDocumentation(it, _string);
          QualifiedName _fullyQualifiedName_1 = Asn1JvmModelInferrer.this._iQualifiedNameProvider.getFullyQualifiedName(element);
          String _lastSegment = _fullyQualifiedName_1.getLastSegment();
          String fieldName = _lastSegment.toLowerCase();
          EList<JvmMember> _members = it.getMembers();
          Type _type = element.getType();
          JvmTypeReference _jvmTypeReference = Asn1JvmModelInferrer.this.toJvmTypeReference(element);
          JvmField _field = Asn1JvmModelInferrer.this._jvmTypesBuilder.toField(_type, fieldName, _jvmTypeReference);
          Asn1JvmModelInferrer.this._jvmTypesBuilder.<JvmField>operator_add(_members, _field);
        }
      };
    _accept.initializeLater(_function);
  }
  
  protected void _infer(final EnumeratedType element, final IJvmDeclaredTypeAcceptor acceptor, final boolean isPrelinkingPhase) {
    QualifiedName _fullyQualifiedName = this._iQualifiedNameProvider.getFullyQualifiedName(element);
    String _string = _fullyQualifiedName.toString();
    final Procedure1<JvmEnumerationType> _function = new Procedure1<JvmEnumerationType>() {
        public void apply(final JvmEnumerationType it) {
          EList<EnumerationItem> _enums = element.getEnums();
          for (final EnumerationItem enumItem : _enums) {
            EList<JvmEnumerationLiteral> _literals = it.getLiterals();
            String _name = enumItem.getName();
            JvmEnumerationLiteral _enumerationLiteral = Asn1JvmModelInferrer.this._jvmTypesBuilder.toEnumerationLiteral(enumItem, _name);
            Asn1JvmModelInferrer.this._jvmTypesBuilder.<JvmEnumerationLiteral>operator_add(_literals, _enumerationLiteral);
          }
        }
      };
    JvmEnumerationType _enumerationType = this._jvmTypesBuilder.toEnumerationType(element, _string, _function);
    acceptor.<JvmEnumerationType>accept(_enumerationType);
  }
  
  protected Pair<String,JvmTypeReference> _toField(final NamedComponentType type) {
    NamedType _namedType = type.getNamedType();
    String _name = _namedType.getName();
    NamedType _namedType_1 = type.getNamedType();
    Type _type = _namedType_1.getType();
    JvmTypeReference _jvmTypeReference = this.toJvmTypeReference(_type);
    Pair<String,JvmTypeReference> _create = Tuples.<String, JvmTypeReference>create(_name, _jvmTypeReference);
    return _create;
  }
  
  protected Pair<String,JvmTypeReference> _toField(final ComponentOfType type) {
    return null;
  }
  
  protected Pair<String,JvmTypeReference> _toField(final AnyDefinedNamedType type) {
    NamedType _definedType = type.getDefinedType();
    String _name = _definedType.getName();
    NamedType _definedType_1 = type.getDefinedType();
    Type _type = _definedType_1.getType();
    JvmTypeReference _jvmTypeReference = this.toJvmTypeReference(_type);
    Pair<String,JvmTypeReference> _create = Tuples.<String, JvmTypeReference>create(_name, _jvmTypeReference);
    return _create;
  }
  
  protected JvmTypeReference _toJvmTypeReference(final Type type) {
    return null;
  }
  
  protected JvmTypeReference _toJvmTypeReference(final ChoiceType type) {
    return null;
  }
  
  protected JvmTypeReference _toJvmTypeReference(final DefinedType type) {
    return null;
  }
  
  protected JvmTypeReference _toJvmTypeReference(final SequenceOfType type) {
    JvmTypeReference _newTypeRef = this._jvmTypesBuilder.newTypeRef(type, "java.util.List");
    return _newTypeRef;
  }
  
  protected JvmTypeReference _toJvmTypeReference(final OctetStringType type) {
    JvmTypeReference _newTypeRef = this._jvmTypesBuilder.newTypeRef(type, "de.thwildau.tm.moses.asn1.lang.OctetString");
    return _newTypeRef;
  }
  
  protected JvmTypeReference _toJvmTypeReference(final IA5String type) {
    JvmTypeReference _newTypeRef = this._jvmTypesBuilder.newTypeRef(type, "java.lang.String");
    return _newTypeRef;
  }
  
  protected JvmTypeReference _toJvmTypeReference(final IntegerType type) {
    JvmTypeReference _newTypeRef = this._jvmTypesBuilder.newTypeRef(type, "java.lang.Integer");
    return _newTypeRef;
  }
  
  public void infer(final EObject element, final IJvmDeclaredTypeAcceptor acceptor, final boolean isPrelinkingPhase) {
    if (element instanceof EnumeratedType) {
      _infer((EnumeratedType)element, acceptor, isPrelinkingPhase);
      return;
    } else if (element instanceof SequenceOfType) {
      _infer((SequenceOfType)element, acceptor, isPrelinkingPhase);
      return;
    } else if (element instanceof SequenceType) {
      _infer((SequenceType)element, acceptor, isPrelinkingPhase);
      return;
    } else if (element != null) {
      _infer(element, acceptor, isPrelinkingPhase);
      return;
    } else {
      throw new IllegalArgumentException("Unhandled parameter types: " +
        Arrays.<Object>asList(element, acceptor, isPrelinkingPhase).toString());
    }
  }
  
  public Pair<String,JvmTypeReference> toField(final ComponentType type) {
    if (type instanceof AnyDefinedNamedType) {
      return _toField((AnyDefinedNamedType)type);
    } else if (type instanceof ComponentOfType) {
      return _toField((ComponentOfType)type);
    } else if (type instanceof NamedComponentType) {
      return _toField((NamedComponentType)type);
    } else {
      throw new IllegalArgumentException("Unhandled parameter types: " +
        Arrays.<Object>asList(type).toString());
    }
  }
  
  public JvmTypeReference toJvmTypeReference(final Type type) {
    if (type instanceof IA5String) {
      return _toJvmTypeReference((IA5String)type);
    } else if (type instanceof ChoiceType) {
      return _toJvmTypeReference((ChoiceType)type);
    } else if (type instanceof IntegerType) {
      return _toJvmTypeReference((IntegerType)type);
    } else if (type instanceof OctetStringType) {
      return _toJvmTypeReference((OctetStringType)type);
    } else if (type instanceof SequenceOfType) {
      return _toJvmTypeReference((SequenceOfType)type);
    } else if (type instanceof DefinedType) {
      return _toJvmTypeReference((DefinedType)type);
    } else if (type != null) {
      return _toJvmTypeReference(type);
    } else {
      throw new IllegalArgumentException("Unhandled parameter types: " +
        Arrays.<Object>asList(type).toString());
    }
  }
}
